-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Idea] Avoid redundant HostPromiseRejectionTrackerEvents #1092
[Idea] Avoid redundant HostPromiseRejectionTrackerEvents #1092
Conversation
0ec4bbe
to
79e4713
Compare
context: https://twitter.com/bmeurer/status/960440074574934016 cc @bmeurer / @domenic / @littledan ## Motivation To mitigate a performance concern @bmeurer [raised](https://twitter.com/bmeurer/status/960440074574934016), related to what I believe are redundant HostPromiseRejectionTrackerEvents. Lets discuss! ## Changes This does change behavior slightly: * Rejections which become handled before the rejections PromiseReactionJob runs will no longer be visible to the HostPromiseRejectionTracker. * The HostPromiseRejectTracker no longer being informed synchronously, rather being informed during the associated PromiseReactionJob. On a positive note, there should be nearly no overhead in this approach. ### Example: ```js Promise.reject().catch(); ``` Would previously emit paired `reject` and `handle` events, and now emits neither. Some in-the-wild code: https://github.com/nodejs/node/blob/d62566e1b1bb4734635d51d53c16f6efa3a7f5b5/lib/internal/process/promises.js#L26-L42 I believe would continue to function as implemented, the difference being `maybeUnhandledPromises` would not contain promises which have become handled by the time their PromiseReactionJob has been processed.
79e4713
to
691ad79
Compare
This seems like a great change to me that reduces that number of times a host would have to clean up after a false unhandled rejection, and minimizes error noise. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really love this simplification, but I'm not sure about the implications for embedders. For example Node collects the stack trace in the "reject"
handler and reuses that later. Not sure how that affects the user experience there.
I share Benedikt's concern about losing stack traces. Not sure whether anyone has implemented this yet, but it may be possible to collect post-mortem dumps from here too. I wonder whether it might be possible to use catch prediction to avoid collecting expensive traces, though this means leaving (at least part of) devtools on all the time. Catch prediction would be less trigger-happy for async functions, but still get tripped up by |
I think this PR wouldn't make any observable changes for the unhandledrejection event (which is how the web platform uses this hook), because notify about rejected promises is executed only after draining the microtask queue, but @domenic would be able to verify this. |
I'd rather implementations make unobservable optimizations like this behind the scenes, instead of requiring spec changes that cascade to host specs. In other words, keep the ES spec as low level as it currently is, instead of trying to do optimizations in spec land. |
Does the committee consider dropping redundant paired reject/handle signals to If so, some guidance for implementors may be required (potentially just some consensus in this thread). As the above discussion suggests, implementers may not have the context, or do not realize they have the freedom make the above change.
@domenic what you say isn't unreasonable, but help me understand more exactly, do you mean:
|
I mean the first. In general, if you can't write a test whose behavior changes with a spec change, then you're making unobservable changes to the spec. And what I'm saying is I'd prefer not to do that. |
If the spec allows for it, I can agree with it. Lets hear from implementors. |
@domenic I suspect the concerns are related to observable differences from the perspective of embedders. Is it fair to say, the spec both doesn't and shouldn't care about differences for embedders? And due to optimizations as suggested above, those differences are just a reality. |
Do we know which implementations would be willing to implement this optimization, in the absence of this PR? |
I think that's going a bit far. What the spec should not care about is how it's observable consequences get implemented. How those changes manifest for the host integration points is subtle and spans multiple specs. As such, I think that any changes to host interfaces should be done in careful collaboration with host specs that are using those interfaces. Here, as far as I can tell, the desired end result is that everyone coordinates on spec changes to produce... No difference in behavior. In that case I'd rather not change things. |
It is not fair to say that the spec doesn't/shouldn't care about host interoperability. We definitely care. However, this seems like the wrong layer to drive this change. What you propose is already fine per the existing spec text. I would rather consider this later with actual interop data and an idea of how changes would positively impact that then to sign up for speccing the actual semantics of promise rejection tracking beyond that such a capability exists. |
This hook is used by multiple ECMAScript embedding environments--not just the Web Platform but also Node.js. For ES implementations with an API which matches the spec's hooks (like V8 with respect to this hook), this would be an observable change, with particular implications for non-Web embedders, even if the Web uses the hook in a way which would not show a change. I don't really see the change as being more low-level or more high-level, it's just different. It's true that ES implementers could expose a different hook to embedders and still ship the ES+HTML semantics anyway, but there's something nice about being able to have some documentation that says, "this API stands in for this heading in the spec text"--you know what you're getting. |
I really like this framing in particular. The existing spec is very minimal and is basically saying "hosts have the ability to track promise rejections". What's being proposed here is getting much further into the details of how specific known hosts (OP motivated by Node.js, and is hopefully compatible with the web too) choose to manifest their rejection tracking, e.g. by batching the notifications and canceling each other out. Those considerations seem best left to the hosts, and to the JS engines that support them. |
@domenic Do you think this PR will change semantics for the Web embedding? |
What do you mean by "semantics"? |
@domenic Would this PR "break" or change how/when onunhandledrejection is sent? My reading: no. But you're the expert here. |
My understanding is that, by coordinating changes across all host environments, this PR would cause no web-observable differences. I haven't spent the time looking into what changes on both sides would be required though to preserve that parity---and I'd rather not do so, as just changing a bunch of things for no observable effect is not, IMO, a good use of my time. |
@bterlson The problem with So unless the JSVM imposes additional restrictions or decides not to implement this part of the spec, it cannot really optimize away/across this host call. I'm not sure yet what is better, at least from V8's perspective (and speaking specifically about the Node use case), but it seems rather unfortunate that you get penalized for attaching a handler to a rejected promise. My personal opinion here is that this shouldn't have been in the spec at all, as it's kinda inconsistent with the other debugging features, for example async stack traces or general debug stepping is all implemented under the hood in the engines/hosts without the spec mentioning it explicitly. So why do we have this particular host interaction specified? I guess there's a reason for that? cc @hashseed |
It seems like there's some confusion about the purpose of this part of the spec. It's not meant to be implemented verbatim, because (like other debugging features you note), it's not observable. What is observable is the unhandledrejection and rejectionhandled events specified in HTML. And those need to get information from the ES spec in order to be well-specified. That's what this hook is for. So as long as the observable consequences (specified in HTML) are the same, the JSVM is fulfilling its duties to its host. The JSVM doesn't need to follow the specification here verbatim. Just like it doesn't need to follow verbatim the specification for Map/Set's List of entry-records. Stated another way, how would you specify HTML's unhandledrejection/rejectionhandled events without this hook? It's part of the interoperable web platform, so it needs to be specified. And it needs this hook to be well-specified. That's why this hook exists: to help specify the observable consequences of HTML's events. Not as some kind of guide to what V8's embedder API should look like. Edit: where does Node fit in? Well, Node doesn't really need this hook, because it doesn't have a specification for interoperable multi-implementation behavior, that needs to build on top of the ES spec. It can just talk directly to V8, using whatever API it wants. It already does that for lots of things, e.g. debugger, REPL, etc., and we don't spec those. How Node wants to communicate promise rejections to V8 is totally up to Node/V8, and not really in the domain of the spec, just like the debugger interactions are not. The hook being in the spec is only necessary for hosts whose behavior is governed by specifications, instead of by directly working with their underlying JS VM. |
@bmeurer you can find additional context in the original PR here: #76. The main motivation was to improve layering between various specifications. I would also note that, "it has to assume that anything can happen while HostPromiseRejectionTracker is running, arbitrary JavaScript can be executed, the state of the system can change completely." is an implementation-specific concern of promise-rejection tracking that allows such script to run. |
My reading was that nothing would have to change on the other side. I understand if you don't have time to look into this right now, though.
Let's keep discussing that issue at nodejs/post-mortem#45 |
@stefanpenner what do you think about closing this for now based on above discussion? |
Works for me, glad this conversation happened! |
context: https://twitter.com/bmeurer/status/960440074574934016
cc @bmeurer / @domenic / @littledan
Motivation
To mitigate a performance concern @bmeurer raised, related to what I believe redundant HostPromiseRejectionTracker events.
Lets discuss!
Changes
This does change behavior slightly:
On a positive note, there should be nearly no overhead in this approach.
Example:
Would previously emit paired
reject
andhandle
events, and now emits neither.Some in-the-wild code: https://github.com/nodejs/node/blob/d62566e1b1bb4734635d51d53c16f6efa3a7f5b5/lib/internal/process/promises.js#L26-L42
I believe would continue to function as implemented, the difference being
maybeUnhandledPromises
would not contain promises which have become handled by the time their PromiseReactionJob has been processed.